/*
 * (C) Copyright 2016 nexell
 * jhkim <jhkim@nexell.co.kr>
 *
 * SPDX-License-Identifier:      GPL-2.0+
 */

#include <common.h>
#include <bootm.h>
#include <command.h>
#include <environment.h>
#include <errno.h>
#include <image.h>
#include <fdt_support.h>

#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_CLI_FRAMEWORK)

DECLARE_GLOBAL_DATA_PTR;

static bootm_headers_t linux_images;

static void boot_go_set_os(cmd_tbl_t *cmdtp, int flag, int argc,
			 char * const argv[], bootm_headers_t *images)
{
	char * const img_addr = argv[0];

	images->os.type = IH_TYPE_KERNEL;
	images->os.comp = IH_COMP_NONE;
	images->os.os = IH_OS_LINUX;
	images->os.load = simple_strtoul(img_addr, NULL, 16);;
	images->ep = images->os.load;
#if defined(CONFIG_ARM)
	images->os.arch = IH_ARCH_ARM;
#elif defined(CONFIG_ARM64)
	images->os.arch = IH_ARCH_ARM64;
#else
	#error "Not support architecture ..."
#endif

#if !defined(CONFIG_OF_LIBFDT) && !defined(CONFIG_SPL_BUILD)
	/* set DTB address for linux kernel */
	if (argc > 2) {
		unsigned long ft_addr;

		ft_addr = simple_strtol(argv[2], NULL, 16);
		images->ft_addr = (char *)ft_addr;

		/*
		 * if not defined IMAGE_ENABLE_OF_LIBFDT,
		 * must be set to fdt address
		 */
		if (!IMAGE_ENABLE_OF_LIBFDT)
			gd->bd->bi_boot_params = ft_addr;

		debug("## set ft:%08lx and boot params:%08lx [control of:%s]" \
			"...\n", ft_addr, gd->bd->bi_boot_params,
			IMAGE_ENABLE_OF_LIBFDT ? "on" : "off");
	}
#endif
}

#if defined(CONFIG_OF_LIBFDT) && defined(CONFIG_LMB)
static void boot_start_lmb(bootm_headers_t *images)
{
	ulong		mem_start;
	phys_size_t	mem_size;

	lmb_init(&images->lmb);

	mem_start = getenv_bootm_low();
	mem_size = getenv_bootm_size();

	lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size);

	arch_lmb_reserve(&images->lmb);
	board_lmb_reserve(&images->lmb);
}
#else
#define lmb_reserve(lmb, base, size)
static inline void boot_start_lmb(bootm_headers_t *images) { }
#endif

int do_boot_linux(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	boot_os_fn *boot_fn;
	bootm_headers_t *images = &linux_images;
	int flags;
	int ret;

	boot_start_lmb(images);

	flags  = BOOTM_STATE_START;

	argc--; argv++;
	boot_go_set_os(cmdtp, flag, argc, argv, images);

#if defined(CONFIG_OF_LIBFDT)
	/* find flattened device tree */
	ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, images,
			   &images->ft_addr, &images->ft_len);
	if (ret) {
		puts("Could not find a valid device tree\n");
		return 1;
	}
	set_working_fdt_addr((ulong)images->ft_addr);
#endif
#if !defined(CONFIG_OF_LIBFDT)
	flags |= BOOTM_STATE_OS_GO;
#endif

	boot_fn = do_bootm_linux;
	ret = boot_fn(flags, argc, argv, images);

	if (ret == BOOTM_ERR_UNIMPLEMENTED)
		show_boot_progress(BOOTSTAGE_ID_DECOMP_UNIMPL);
	else if (ret == BOOTM_ERR_RESET)
		do_reset(cmdtp, flag, argc, argv);

	return ret;
}

U_BOOT_CMD(
	bootl,	CONFIG_SYS_MAXARGS,	1,	do_boot_linux,
	"boot linux image from memory",
	"[addr [arg ...]]\n    - boot linux image stored in memory\n"
	"\tuse a '-' for the DTB address\n"
);
#endif

#if defined(CONFIG_CMD_BOOTD) && !defined(CONFIG_CMD_BOOTM)
int do_bootd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	return run_command(getenv("bootcmd"), flag);
}

U_BOOT_CMD(
	boot,	1,	1,	do_bootd,
	"boot default, i.e., run 'bootcmd'",
	""
);

/* keep old command name "bootd" for backward compatibility */
U_BOOT_CMD(
	bootd, 1,	1,	do_bootd,
	"boot default, i.e., run 'bootcmd'",
	""
);
#endif
